﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MainWindow.xaml.cs" company="Microsoft">
//   Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace TrackingStateMachine
{
    using System;
    using System.Activities;
    using System.Activities.DurableInstancing;
    using System.Activities.Tracking;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Linq;
    using System.Runtime.DurableInstancing;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;

    using Microsoft.Activities.Extensions.DurableInstancing;
    using Microsoft.Activities.Extensions.Tracking;

    /// <summary>
    ///   Interaction logic for MainWindow.xaml
    /// </summary>
    public sealed partial class MainWindow : INotifyPropertyChanged, IWorkflowView, IDisposable
    {
        #region Constants

        /// <summary>
        ///   The connection string
        /// </summary>
        private const string ConnString =
            @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=" + SampleInstanceStore
            + ";Integrated Security=True;Asynchronous Processing=True";

        /// <summary>
        ///   The name of the database
        /// </summary>
        private const string SampleInstanceStore = "SampleInstanceStore";

        #endregion

        #region Fields

        /// <summary>
        ///   The model.
        /// </summary>
        private readonly WorkflowModel model;

        /// <summary>
        ///   The instance store.
        /// </summary>
        private SqlWorkflowInstanceStore instanceStore;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        ///   Initializes a new instance of the <see cref="MainWindow" /> class.
        /// </summary>
        public MainWindow()
        {
            this.NewCommand = new RelayCommand(this.ExecuteNew, this.CanExecuteNew);
            this.LoadCommand = new RelayCommand(this.ExecuteLoad, this.CanExecuteLoad);
            this.OpenCommand = new RelayCommand(this.ExecuteOpen, this.CanExecuteOpen);
            this.ClearCommand = new RelayCommand(this.ExecuteClear, this.CanExecuteClear);
            this.UnloadCommand = new RelayCommand(this.ExecuteUnload, this.CanExecuteUnload);
            this.T1Command = new RelayCommand(this.ExecuteT1, this.CanExecuteT1);
            this.T2Command = new RelayCommand(this.ExecuteT2, this.CanExecuteT2);
            this.T3Command = new RelayCommand(this.ExecuteT3, this.CanExecuteT3);
            this.T4Command = new RelayCommand(this.ExecuteT4, this.CanExecuteT4);
            this.T5Command = new RelayCommand(this.ExecuteT5, this.CanExecuteT5);
            this.T6Command = new RelayCommand(this.ExecuteT6, this.CanExecuteT6);
            this.TrueCommand = new RelayCommand(this.ExecuteTrue);
            this.FalseCommand = new RelayCommand(this.ExecuteFalse);

            this.Loaded += this.MainWindowLoaded;
            this.TrackingBuilder = new StringBuilder();
            Trace.Listeners.Add(new TextListener(this));

            this.model = new WorkflowModel(this);
            this.Closing += this.model.ViewClosing;
            this.Closed += this.model.ViewClosed;

            this.InitializeComponent();
        }

        

        #endregion

        #region Public Events

        /// <summary>
        ///   The property changed.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Public Properties

        /// <summary>
        ///   Gets the Clear command
        /// </summary>
        public ICommand ClearCommand { get; private set; }

        /// <summary>
        ///   Gets ConnectionString.
        /// </summary>
        public string ConnectionString
        {
            get
            {
                return ConnString;
            }
        }

        /// <summary>
        ///   Gets CurrentState.
        /// </summary>
        public string CurrentState
        {
            get
            {
                return this.Model != null ? this.Model.CurrentState : string.Empty;
            }
        }

        /// <summary>
        ///   Gets DatabaseName.
        /// </summary>
        public string DatabaseName
        {
            get
            {
                return SampleInstanceStore;
            }
        }

        /// <summary>
        ///   Gets InstanceStore.
        /// </summary>
        public InstanceStore InstanceStore
        {
            get
            {
                return this.instanceStore;
            }
        }

        /// <summary>
        ///   Gets the Load command
        /// </summary>
        public ICommand LoadCommand { get; private set; }

        /// <summary>
        ///   Gets Model.
        /// </summary>
        public WorkflowModel Model
        {
            get
            {
                return this.model;
            }
        }

        /// <summary>
        ///   Gets the New command
        /// </summary>
        public ICommand NewCommand { get; private set; }

        /// <summary>
        ///   Gets the Load command
        /// </summary>
        public ICommand OpenCommand { get; private set; }

        /// <summary>
        ///   Gets or sets SelectedIndex.
        /// </summary>
        public int SelectedIndex
        {
            get
            {
                return this.Model != null ? this.Model.SelectedIndex : -1;
            }

            set
            {
                this.Model.SelectedIndex = value;
            }
        }

        /// <summary>
        ///   Gets CurrentStateMachineName.
        /// </summary>
        public string StateMachineName
        {
            get
            {
                return this.Model != null ? this.Model.CurrentStateMachineName : string.Empty;
            }
        }

        /// <summary>
        ///   Gets T1Command.
        /// </summary>
        public ICommand T1Command { get; private set; }

        /// <summary>
        ///   Gets T2Command.
        /// </summary>
        public ICommand T2Command { get; private set; }

        /// <summary>
        ///   Gets T3Command.
        /// </summary>
        public ICommand T3Command { get; private set; }

        /// <summary>
        ///   Gets T4Command.
        /// </summary>
        public ICommand T4Command { get; private set; }

        /// <summary>
        ///   Gets T5Command.
        /// </summary>
        public ICommand T5Command { get; private set; }

        /// <summary>
        ///   Gets T6Command.
        /// </summary>
        public ICommand T6Command { get; private set; }

        /// <summary>
        ///   Gets Tracking.
        /// </summary>
        public string Tracking
        {
            get
            {
                return this.TrackingBuilder != null ? this.TrackingBuilder.ToString() : null;
            }
        }

        /// <summary>
        ///   Gets the Unload command
        /// </summary>
        public ICommand UnloadCommand { get; private set; }

        /// <summary>
        ///   Gets Workflows.
        /// </summary>
        public ObservableCollection<WorkflowInstance> Workflows
        {
            get
            {
                return this.Model != null ? this.Model.Workflows : null;
            }
        }

        #endregion

        #region Properties

        /// <summary>
        ///   Gets TrackingBuilder.
        /// </summary>
        private StringBuilder TrackingBuilder { get; set; }
        public RelayCommand TrueCommand { get; private set; }
        public RelayCommand FalseCommand { get; private set; }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        /// </summary>
        public void Dispose()
        {
            var workflowModel = this.model;
            if (workflowModel != null)
            {
                workflowModel.Dispose();
            }
        }

        /// <summary>
        ///   Initializes the instance store
        /// </summary>
        public void InitializeInstanceStore()
        {
            Trace.WriteLine("Verifying instance store " + this.DatabaseName);
            if (!SqlWorkflowInstanceStoreManager.InstanceStoreExists(this.DatabaseName, this.ConnectionString))
            {
                Trace.WriteLine("Creating instance store " + this.DatabaseName);
                SqlWorkflowInstanceStoreManager.CreateInstanceStore(this.DatabaseName, this.ConnectionString);
            }

            this.instanceStore = new SqlWorkflowInstanceStore(this.ConnectionString);

            StateMachineStateTracker.Promote(this.instanceStore);

            var createWorkflowOwnerCommand = new CreateWorkflowOwnerCommand();
            var handle = this.instanceStore.CreateInstanceHandle();

            try
            {
                this.instanceStore.BeginExecute(
                    handle, createWorkflowOwnerCommand, TimeSpan.FromSeconds(30), this.OnInstanceStoreEndExecute, null);
            }
            catch (InstancePersistenceException persistenceException)
            {
                this.WriteException(persistenceException, "An error has occured setting up the InstanceStore");
            }
        }

        /// <summary>
        /// The notify changed.
        /// </summary>
        /// <param name="property">
        /// The property. 
        /// </param>
        public void NotifyChanged(string property)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(property));
            }
        }

        /// <summary>
        /// The on abort.
        /// </summary>
        /// <param name="args">
        /// The args. 
        /// </param>
        public void OnAbort(WorkflowApplicationAbortedEventArgs args)
        {
            this.RefeshWorkflowState();
        }

        /// <summary>
        /// The on complete.
        /// </summary>
        /// <param name="args">
        /// The args. 
        /// </param>
        public void OnComplete(WorkflowApplicationCompletedEventArgs args)
        {
            Dispatch(this.Model.OnComplete);

            this.RefeshWorkflowState();
        }

        /// <summary>
        /// The idle handler.
        /// </summary>
        /// <param name="args">
        /// The workflow application idle event args. 
        /// </param>
        public void OnIdle(WorkflowApplicationIdleEventArgs args)
        {
            this.RefeshWorkflowState();
        }

        /// <summary>
        /// The on unhandled exception.
        /// </summary>
        /// <param name="args">
        /// The args. 
        /// </param>
        /// <returns>
        /// The unhandled exception action. 
        /// </returns>
        public UnhandledExceptionAction OnUnhandledException(WorkflowApplicationUnhandledExceptionEventArgs args)
        {
            this.RefeshWorkflowState();
            MessageBox.Show(
                this, args.UnhandledException.Message, "Unhandled Exception", MessageBoxButton.OK, MessageBoxImage.Stop);

            return UnhandledExceptionAction.Abort;
        }

        /// <summary>
        /// The on unload.
        /// </summary>
        /// <param name="obj">
        /// The obj. 
        /// </param>
        public void OnUnload(WorkflowApplicationEventArgs obj)
        {
            Trace.WriteLine("Workflow unloaded");
        }

        /// <summary>
        /// The persistable idle.
        /// </summary>
        /// <param name="arg">
        /// The arg. 
        /// </param>
        /// <returns>
        /// The persistable idle action 
        /// </returns>
        public PersistableIdleAction PersistableIdle(WorkflowApplicationIdleEventArgs arg)
        {
            return PersistableIdleAction.Persist;
        }

        /// <summary>
        /// The write exception.
        /// </summary>
        /// <param name="exception">
        /// The exception. 
        /// </param>
        /// <param name="error">
        /// The error. 
        /// </param>
        public void WriteException(Exception exception, string error)
        {
            Trace.WriteLine(error);
            Trace.WriteLine(exception.Message);
        }

        #endregion

        #region Methods

        /// <summary>
        /// The dispatch.
        /// </summary>
        /// <param name="action">
        /// The action. 
        /// </param>
        private static void Dispatch(Action action)
        {
            if (Application.Current != null)
            {
                Application.Current.Dispatcher.BeginInvoke(action);
            }
        }

        /// <summary>
        ///   The requery commands.
        /// </summary>
        private static void RequeryCommands()
        {
            Dispatch(CommandManager.InvalidateRequerySuggested);
        }

        /// <summary>
        /// The can execute clear.
        /// </summary>
        /// <param name="obj">
        /// The obj. 
        /// </param>
        /// <returns>
        /// True if clear can execute 
        /// </returns>
        private bool CanExecuteClear(object obj)
        {
            return this.TrackingBuilder.Length > 0;
        }

        /// <summary>
        /// Determines if the Load command can be executed
        /// </summary>
        /// <param name="obj">
        /// The obj. 
        /// </param>
        /// <returns>
        /// true, if the Load command is enabled, false if not. 
        /// </returns>
        private bool CanExecuteLoad(object obj)
        {
            return this.Model.SelectedIndex != -1 && !this.Model.IsLoaded;
        }

        /// <summary>
        /// Determines if the new command can be executed
        /// </summary>
        /// <param name="obj">
        /// The obj. 
        /// </param>
        /// <returns>
        /// true, if the new command is enabled, false if not. 
        /// </returns>
        private bool CanExecuteNew(object obj)
        {
            return true;
        }

        /// <summary>
        /// Determines if the Open command can be executed
        /// </summary>
        /// <param name="obj">
        /// The obj. 
        /// </param>
        /// <returns>
        /// true, if the Open command is enabled, false if not. 
        /// </returns>
        private bool CanExecuteOpen(object obj)
        {
            return true;
        }

        /// <summary>
        /// Determines if the T1 transition can execute
        /// </summary>
        /// <param name="obj">
        /// The obj. 
        /// </param>
        /// <returns>
        /// True if the T1 transition can execute 
        /// </returns>
        private bool CanExecuteT1(object obj)
        {
            return this.CanExecuteTransition("T1");
        }

        /// <summary>
        /// Determines if the T2 transition can execute
        /// </summary>
        /// <param name="obj">
        /// The obj. 
        /// </param>
        /// <returns>
        /// True if the T2 transition can execute 
        /// </returns>
        private bool CanExecuteT2(object obj)
        {
            return this.CanExecuteTransition("T2");
        }

        /// <summary>
        /// Determines if the T3 transition can execute
        /// </summary>
        /// <param name="obj">
        /// The obj. 
        /// </param>
        /// <returns>
        /// True if the T3 transition can execute 
        /// </returns>
        private bool CanExecuteT3(object obj)
        {
            return this.CanExecuteTransition("T3");
        }

        /// <summary>
        /// Determines if the T4 transition can execute
        /// </summary>
        /// <param name="obj">
        /// The obj. 
        /// </param>
        /// <returns>
        /// True if the T4 transition can execute 
        /// </returns>
        private bool CanExecuteT4(object obj)
        {
            return this.CanExecuteTransition("T4");
        }

        /// <summary>
        /// Determines if the T5 transition can execute
        /// </summary>
        /// <param name="obj">
        /// The obj. 
        /// </param>
        /// <returns>
        /// True if the T5 transition can execute 
        /// </returns>
        private bool CanExecuteT5(object obj)
        {
            return this.CanExecuteTransition("T5");
        }

        /// <summary>
        /// Determines if the T6 transition can execute
        /// </summary>
        /// <param name="obj">
        /// The obj. 
        /// </param>
        /// <returns>
        /// True if the T6 transition can execute 
        /// </returns>
        private bool CanExecuteT6(object obj)
        {
            return this.CanExecuteTransition("T6");
        }

        /// <summary>
        /// Determines if a transition can execute
        /// </summary>
        /// <param name="transition">
        /// The transition. 
        /// </param>
        /// <returns>
        /// True if the transition can execute 
        /// </returns>
        private bool CanExecuteTransition(string transition)
        {
            return this.Model.Transitions != null && this.Model.Transitions.Any(t => t.DisplayName.Equals(transition));
        }

        /// <summary>
        /// Determines if the Unload command can be executed
        /// </summary>
        /// <param name="obj">
        /// The obj. 
        /// </param>
        /// <returns>
        /// true, if the Unload command is enabled, false if not. 
        /// </returns>
        private bool CanExecuteUnload(object obj)
        {
            return this.Model.IsLoaded;
        }

        /// <summary>
        /// The execute clear.
        /// </summary>
        /// <param name="obj">
        /// The obj. 
        /// </param>
        private void ExecuteClear(object obj)
        {
            this.TrackingBuilder.Clear();
            this.NotifyChanged("Tracking");
        }

        /// <summary>
        /// Executes a Load workflow
        /// </summary>
        /// <param name="obj">
        /// The obj. 
        /// </param>
        private void ExecuteLoad(object obj)
        {
            this.Model.Load();
            this.listInstances.Focus();
            this.listInstances.ScrollIntoView(this.listInstances.SelectedItem);
        }

        /// <summary>
        /// Executes a new workflow
        /// </summary>
        /// <param name="obj">
        /// The obj. 
        /// </param>
        private void ExecuteNew(object obj)
        {
            this.Model.New();
            this.listInstances.Focus();
            this.listInstances.ScrollIntoView(this.listInstances.SelectedItem);
        }

        /// <summary>
        /// Executes a Open workflow
        /// </summary>
        /// <param name="obj">
        /// The obj. 
        /// </param>
        private void ExecuteOpen(object obj)
        {
            var dialog = new DialogOpen();
            var result = dialog.ShowDialog();
            if (result.GetValueOrDefault(false))
            {
                Guid instanceId;
                if (Guid.TryParse(dialog.textId.Text, out instanceId))
                {
                    this.Model.Open(instanceId);
                }
                else
                {
                    MessageBox.Show(this, "Invalid Guid", "Error", MessageBoxButton.OK, MessageBoxImage.Stop);
                }
            }

            this.listInstances.Focus();
            this.listInstances.ScrollIntoView(this.listInstances.SelectedItem);
        }

        /// <summary>
        /// Executes the T1 transition
        /// </summary>
        /// <param name="obj">
        /// The obj. 
        /// </param>
        private void ExecuteT1(object obj)
        {
            this.Model.ResumeBookmark("T1");
        }

        /// <summary>
        /// Executes the T2 transition
        /// </summary>
        /// <param name="obj">
        /// The obj. 
        /// </param>
        private void ExecuteT2(object obj)
        {
            this.Model.ResumeBookmark("T2");
        }

        /// <summary>
        /// Executes the T3 transition
        /// </summary>
        /// <param name="obj">
        /// The obj. 
        /// </param>
        private void ExecuteT3(object obj)
        {
            this.Model.ResumeBookmark("T3");
        }

        /// <summary>
        /// Executes the T4 transition
        /// </summary>
        /// <param name="obj">
        /// The obj. 
        /// </param>
        private void ExecuteT4(object obj)
        {
            this.Model.ResumeBookmark("T4");
        }

        /// <summary>
        /// Executes the T5 transition
        /// </summary>
        /// <param name="obj">
        /// The obj. 
        /// </param>
        private void ExecuteT5(object obj)
        {
            this.Model.ResumeBookmark("T5");
        }

        /// <summary>
        /// Executes the T6 transition
        /// </summary>
        /// <param name="obj">
        /// The obj. 
        /// </param>
        private void ExecuteT6(object obj)
        {
            this.Model.ResumeBookmark("T6");
        }


        private void ExecuteFalse(object obj)
        {
            this.Model.ResumeBookmark(this.model.CurrentState,"0");
        }

        private void ExecuteTrue(object obj)
        {
            this.Model.ResumeBookmark(this.model.CurrentState, "1");
        }

        /// <summary>
        /// Executes a Unload workflow
        /// </summary>
        /// <param name="obj">
        /// The obj. 
        /// </param>
        private void ExecuteUnload(object obj)
        {
            this.Model.Unload();
            this.listInstances.Focus();
            this.listInstances.ScrollIntoView(this.listInstances.SelectedItem);
        }

        /// <summary>
        /// The main window has been loaded
        /// </summary>
        /// <param name="sender">
        /// The sender. 
        /// </param>
        /// <param name="e">
        /// The e. 
        /// </param>
        private void MainWindowLoaded(object sender, RoutedEventArgs e)
        {
            this.InitializeInstanceStore();
            this.Model.LoadInstances(this);
        }

        /// <summary>
        /// The on instance store end execute.
        /// </summary>
        /// <param name="ar">
        /// The ar. 
        /// </param>
        private void OnInstanceStoreEndExecute(IAsyncResult ar)
        {
            InstanceView ownerView;

            try
            {
                ownerView = this.instanceStore.EndExecute(ar);
            }
            catch (InstancePersistenceException persistenceException)
            {
                this.WriteException(persistenceException, "An error has occured setting up the InstanceStore");
                return;
            }

            this.instanceStore.DefaultInstanceOwner = ownerView.InstanceOwner;
        }

        /// <summary>
        ///   The refesh workflow state.
        /// </summary>
        private void RefeshWorkflowState()
        {
            // Update the command bindings
            RequeryCommands();

            // Update the current state
            this.NotifyChanged("StateMachineName");
            this.NotifyChanged("CurrentState");
            if (this.listInstances != null)
            {
                Dispatch(() => this.listInstances.Focus());
            }
        }

        /// <summary>
        ///   The scroll to end.
        /// </summary>
        private void ScrollToEnd()
        {
            if (this.ScrollViewerTrace != null)
            {
                Dispatch(this.ScrollViewerTrace.ScrollToEnd);
            }
        }

        /// <summary>
        /// The track.
        /// </summary>
        /// <param name="message">
        /// The message. 
        /// </param>
        private void Track(string message)
        {
            this.TrackingBuilder.AppendLine(message);
            if (!message.StartsWith("Notify changed:"))
            {
                this.NotifyChanged("Tracking");
            }

            this.ScrollToEnd();
        }

        /// <summary>
        /// The workflow selection changed.
        /// </summary>
        /// <param name="sender">
        /// The sender. 
        /// </param>
        /// <param name="e">
        /// The e. 
        /// </param>
        private void WorkflowSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            this.RefeshWorkflowState();
        }

        #endregion

        /// <summary>
        ///   The text tracker.
        /// </summary>
        internal class TextTracker : TrackingParticipant
        {
            #region Methods

            /// <summary>
            /// The track.
            /// </summary>
            /// <param name="record">
            /// The record. 
            /// </param>
            /// <param name="timeout">
            /// The timeout. 
            /// </param>
            protected override void Track(TrackingRecord record, TimeSpan timeout)
            {
                record.Trace();
            }

            #endregion
        }

        /// <summary>
        ///   The text listener.
        /// </summary>
        private class TextListener : TraceListener
        {
            #region Fields

            /// <summary>
            ///   The main window.
            /// </summary>
            private readonly MainWindow mainWindow;

            #endregion

            #region Constructors and Destructors

            /// <summary>
            /// Initializes a new instance of the <see cref="TextListener"/> class. 
            ///   Initializes a new instance of the <see cref="TextListener"/> class. 
            ///   Initializes a new instance of the <see cref="TextListener"/> class. 
            ///   Initializes a new instance of the <see cref="TextListener"/> class. 
            ///   Initializes a new instance of the <see cref="TextListener"/> class. 
            ///   Initializes a new instance of the <see cref="TextListener"/> class. 
            ///   Initializes a new instance of the <see cref="TextListener"/> class. 
            ///   Initializes a new instance of the <see cref="TextListener"/> class. 
            ///   Initializes a new instance of the <see cref="TextListener"/> class. 
            ///   Initializes a new instance of the <see cref="TextListener"/> class. 
            ///   Initializes a new instance of the <see cref="TextListener"/> class. 
            ///   Initializes a new instance of the <see cref="TextListener"/> class.
            /// </summary>
            /// <param name="mainWindow">
            /// The main window. 
            /// </param>
            internal TextListener(MainWindow mainWindow)
            {
                this.mainWindow = mainWindow;
            }

            #endregion

            #region Public Methods and Operators

            /// <summary>
            /// When overridden in a derived class, writes the specified message to the listener you create in the derived class.
            /// </summary>
            /// <param name="message">
            /// A message to write. 
            /// </param>
            /// <filterpriority>2</filterpriority>
            public override void Write(string message)
            {
                if (message.StartsWith("WorkflowDebugger"))
                {
                    return;
                }

                this.mainWindow.Track(message);
            }

            /// <summary>
            /// When overridden in a derived class, writes a message to the listener you create in the derived class, followed by a line terminator.
            /// </summary>
            /// <param name="message">
            /// A message to write. 
            /// </param>
            /// <filterpriority>2</filterpriority>
            public override void WriteLine(string message)
            {
                if (message.StartsWith("WorkflowDebugger"))
                {
                    return;
                }

                this.mainWindow.Track(message);
            }

            #endregion
        }
    }
}